package com.devops.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.Method;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.devops.admin.mapper.DOSHelmChartMapper;
import com.devops.admin.model.*;
import com.devops.admin.po.DOSChartImportPo;
import com.devops.admin.po.DOSChartParamPo;
import com.devops.admin.po.DOSChartPo;
import com.devops.admin.po.DOSRolePo;
import com.devops.admin.service.*;
import com.devops.admin.util.Assert;
import com.devops.admin.util.HuHttpUtil;
import com.devops.admin.util.ThreadPoolSingleton;
import com.devops.admin.vo.DOSChartParamVo;
import com.devops.admin.vo.DOSPageVo;
import com.devops.constant.DOSRoleEnum;
import com.devops.constant.DevOpsConstant;
import com.devops.shiro.vm.BaseUserVM;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @ClassNem DOSHelmChartServiceImpl
 * @Description TODO
 * @Author heyabo
 * @Date 2022/3/23 15:11
 * @Version 1.0
 **/
@Service
@Slf4j
public class DOSHelmChartServiceImpl extends ServiceImpl<DOSHelmChartMapper, DOSHelmChart> implements DOSHelmChartService {

    @Autowired
    private DOSHelmChartUserRelationService dosHelmChartUserRelationService;
    @Autowired
    private DOSHelmChartRepositoryService dosHelmChartRepositoryService;
    @Autowired
    private DOSClusterInfoService dosClusterInfoService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DOSChartParamPo saveChart(DOSHelmChart chart) {
        if (chart.getChartType().intValue()==DevOpsConstant.ChartType.PRIVATE){
            Assert.isTrue(null!=chart.getProjectId() && chart.getProjectId() > 0,"私有的chart项目id不能为空");
        }
        BaseUserVM user = (BaseUserVM) SecurityUtils.getSubject().getPrincipal();
        // 校验人员是否有操作权限
        checkUserOperationsAuthority(user);

        LambdaQueryWrapper<DOSHelmChart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(DOSHelmChart::getChartName,chart.getChartName())
                    .eq(DOSHelmChart::getChartRepositoryName,chart.getChartRepositoryName());
        DOSHelmChart helmCharts = baseMapper.selectOne(queryWrapper);

        DOSChartParamPo po = new DOSChartParamPo();
        if (null != helmCharts){
            // 如果是重复创建则进行更新
            helmCharts.setChartTag(chart.getChartTag());
            if (chart.getProjectId() != null && chart.getProjectId() > 0){
                helmCharts.setChartType(chart.getChartType());
                helmCharts.setProjectId(chart.getProjectId());
            }
            // 更新chart
            this.updateChart(helmCharts);
        } else {
            //验证chart
            chart.setChartStatus(DevOpsConstant.HelmCheckType.CHECKING);
            boolean save = this.save(chart);
            //异步添加或更新chart状态
            execAsync(chart, queryWrapper);
            //添加chart的人员关系表
            DOSHelmChartUserRelation relation = new DOSHelmChartUserRelation();
            relation.setChartId(chart.getId());
            relation.setUserId(user.getId());
            relation.setUserType(DevOpsConstant.UserType.CREATE);
            boolean b = dosHelmChartUserRelationService.save(relation);
            // 创建成功
            if (save && b) {
                DOSChartParamVo vo = new DOSChartParamVo();
                vo.setUserId(user.getId());
                vo.setId(chart.getId());
                List<DOSChartParamPo> list = baseMapper.getChartList(new Page<>(1, 10), vo);
                if (CollectionUtil.isNotEmpty(list)) {
                    BeanUtil.copyProperties(list.get(0), po);
                }
            }
        }
        return po;
    }

    @Override
    public TableResult<DOSChartParamPo> getCharts(DOSChartParamVo paramVo, DOSPageVo pageVo) {
        BaseUserVM user = (BaseUserVM) SecurityUtils.getSubject().getPrincipal();
        // 获取人员的角色code列表
        List<String> roleCodes = user.getRoles().stream().map(DOSRolePo::getRoleCode).collect(Collectors.toList());
        // 分页查询提前数据准备
        Page<DOSChartParamPo> page = new Page<>(pageVo.getPageNum(),pageVo.getPageSize());
        TableResult<DOSChartParamPo> tableResult = new TableResult<>();
        List<DOSChartParamPo> chartList;
        // 如果是管理员级别或者是QA则可以查看全部的信息
        if (user.getAdminRole()) {
            chartList = baseMapper.getChartAdminList(page, paramVo);
        }else if (roleCodes.contains(DOSRoleEnum.TECHNOLOGY_MANAGER.getRoleCode())
                || roleCodes.contains(DOSRoleEnum.JAVA_ENGINEER.getRoleCode())){
            // 如果是技术经理和项目开发则只能看到公共的和自己所在的项目组项目关联的私有chart
            paramVo.setUserId(user.getId());
            chartList = baseMapper.getChartList(page, paramVo);
        }else{
            // 不是管理员级别的、技术经理，开发人员直接返回空数据
            return tableResult;
        }
        //添加人员操作chart的标志，1：可以操作、2：无法操作
        chartList.forEach(chart->{
            // 如果人员的userId和chart的创建人的userId一致，则有操作权限
            if (user.getAdminRole()){
                // 管理员级别的人员对chart有操作权限
                chart.setActionFlag(true);
            }else {
                chart.setActionFlag(user.getId().equals(chart.getUserId()));
            }
        });
        //按照创建的时间进行降序排序 - 不知道的原因sql里的排序没有起作用
        chartList = chartList.stream().sorted(Comparator.comparing(DOSChartParamPo::getCreateTime).reversed()).collect(Collectors.toList());
        Pagination p =  Pagination.builder().pageNum(page.getCurrent()).pageSize(page.getSize()).total(page.getTotal()).build();
        tableResult.setList(chartList);
        tableResult.setPagination(p);
        return tableResult;
    }

    @Override
    public Boolean updateChart(DOSHelmChart chart) {
        BaseUserVM user = (BaseUserVM) SecurityUtils.getSubject().getPrincipal();
        // 校验人员是否有操作权限
        checkUserOperationsAuthority(user);
        // 对私有仓库进行判断
        if (chart.getChartType().intValue()==DevOpsConstant.ChartType.PRIVATE){
            Assert.isTrue(null!=chart.getProjectId() && chart.getProjectId() > 0,"私有的chart项目id不能为空");
        }else {
            // 这里是因为mybatisPlus框架无法对空的数据进行更新处理
            chart.setProjectId(0);
            chart.setModuleId(0);
        }
        // 设置chart的状态为正在检查状态
        chart.setChartStatus(DevOpsConstant.HelmCheckType.CHECKING);
        baseMapper.updateById(chart);
        //异步处理，更新chart的状态
        execAsync(chart,null);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteChart(Integer chartId) {
        BaseUserVM user = (BaseUserVM) SecurityUtils.getSubject().getPrincipal();
        // 校验人员是否有操作权限
        checkUserOperationsAuthority(user);
        //
        DOSHelmChart chart = this.getById(chartId);
        Assert.isTrue(null!=chart,"未查询到chart");
        // 如果人员非管理员级别的身份
        if (!user.getAdminRole()) {
            LambdaQueryWrapper<DOSHelmChartUserRelation> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(DOSHelmChartUserRelation::getChartId, chartId)
                    .eq(DOSHelmChartUserRelation::getUserId, user.getId());
            // 先判断人员是否有删除的权限 - 自己创建的可以自己删除，其他的不可以删除
            DOSHelmChartUserRelation one = dosHelmChartUserRelationService.getOne(queryWrapper);
            Assert.isTrue(one.getUserType().intValue() == DevOpsConstant.UserType.CREATE || user.getAdminRole(), "无删除权限");
            // 删除chart与人员的关系
            dosHelmChartUserRelationService.remove(queryWrapper);
        }
        // 删除chart
        return this.removeById(chartId);
    }

    @Override
    public List<DOSChartPo> getDeployCharts(Integer projectId) {
        // 获取可以用于部署的chart
        return baseMapper.getDeployCharts(projectId);
    }

    @Override
    public List<DOSHelmChartRepository> getEnvRepositories() {

        return dosHelmChartRepositoryService.list(Wrappers.<DOSHelmChartRepository>lambdaQuery()
                .eq(DOSHelmChartRepository::getRepositoryStatus,DevOpsConstant.CheckType.CHECK_SUCCESS));
    }

    @Override
    public DOSChartImportPo importHelm(MultipartFile file,String chart) {
        BaseUserVM user = (BaseUserVM) SecurityUtils.getSubject().getPrincipal();
        // 校验人员是否有操作权限
        checkUserOperationsAuthority(user);

        String data = "data";
        String code = "code";
        JSONObject jsonObject = JSONUtil.parseObj(chart);
        DOSHelmChart dosHelmChart = JSONUtil.toBean(jsonObject, DOSHelmChart.class);
        Assert.isTrue(StrUtil.isNotBlank(dosHelmChart.getChartRepositoryName()),"仓库名称不能为空");
        Assert.isTrue(null!=file,"文件不存在");
        String helmChartUrl = dosClusterInfoService.getOneAvailableHelmWrapperAPIUrl();
        Assert.isTrue(StrUtil.isNotBlank(helmChartUrl),"未查询到helmChartUrl地址");
        DOSHelmChartRepository byId = dosHelmChartRepositoryService.getById(dosHelmChart.getChartRepositoryId());
        Assert.isTrue(null!=byId,"仓库记录不存在或仓库记录被删除");

        DOSChartImportPo po = new DOSChartImportPo();
        //组装url
        String api = DevOpsConstant.HelmWrapperAPIUrl.HELM_UPLOAD_CHART_REPO_POST;
        // 去除请求的url中的空格，不然请求报错
        String url = (helmChartUrl+api).replaceAll(" ","");

        //"Pushing testchart-0.2.0.tgz to 135884-ijx_uat...\nDone.\n"
        // 上传chart
        JSONObject json = HuHttpUtil.upload(dosHelmChart.getChartRepositoryName(),file,url);
        if (null!=json && json.getInt(code)==0 && json.containsKey(data)){
            String msg = json.getStr(data);
            if (StrUtil.isNotBlank(msg)) {
                Matcher matcher = getNumbers(msg);
                if (matcher.find()){
                    po.setChartName(matcher.group(1));
                    po.setChartTag(matcher.group(2));
                }
            }
        }else{
            return null;
        }
        // 更新chart
        if (dosHelmChart.getId() != null && dosHelmChart.getId() > 0){
            DOSHelmChart dosHelmCharts = this.getById(dosHelmChart.getId());
            dosHelmCharts.setChartTag(po.getChartTag());
            if (dosHelmChart.getProjectId() != null && dosHelmChart.getProjectId() > 0){
                dosHelmCharts.setProjectId(dosHelmChart.getProjectId());
                dosHelmCharts.setChartType(dosHelmChart.getChartType());
            }
            this.updateById(dosHelmCharts);
            // 验证远程chart
            execAsync(dosHelmCharts,null);
        } else{
            // 保存新创建的chart
            dosHelmChart.setChartName(po.getChartName());
            dosHelmChart.setChartTag(po.getChartTag());
            this.saveChart(dosHelmChart);
        }

        return po;
    }

    /**
     * @Author heyabo
     * @Description 校验人员是否有操作权限
     * @Date 10:39 2022/9/8
     * @Param [user]
     * @return void
     **/
    private void checkUserOperationsAuthority(BaseUserVM user) {
        // 获取人员角色code码
        List<String> roleCodes = user.getRoles().stream().map(DOSRolePo::getRoleCode).collect(Collectors.toList());
        // QA没有添加chart的权限
        Assert.isTrue(user.getAdminRole()
                || roleCodes.contains(DOSRoleEnum.TECHNOLOGY_MANAGER.getRoleCode())
                || roleCodes.contains(DOSRoleEnum.JAVA_ENGINEER.getRoleCode()),"没有创建的权限");
    }

    /**
     * @Author heyabo
     * @Description 验证chart
     * @Date 16:56 2022/3/23
     * @Param [chartName, repoName]
     * @return java.lang.String
     **/
    private Integer checkChart(DOSHelmChart chart){
        Integer result = DevOpsConstant.HelmCheckType.CHECK_FAILURE;
        String chartName = chart.getChartRepositoryName() + "/" + chart.getChartName();

        String helmWrapperDomain = dosClusterInfoService.getOneAvailableHelmWrapperAPIUrl();
        if (StrUtil.isBlank(helmWrapperDomain)){
            return result;
        }

        //更新仓库
        String helmUpdateRepoUrl = (helmWrapperDomain + DevOpsConstant.HelmWrapperAPIUrl.HELM_REPO_UPDATE_PUT).replaceAll(" ","");
        JSONObject responseJson = HuHttpUtil.dealHttpAndResponse(Method.PUT, helmUpdateRepoUrl.trim(), null, null);
        if (responseJson.getInt("code") != 0){
            return result;
        }

        //查询远程仓库
        String helmSearchRepoUrl = helmWrapperDomain.trim()
                + StrUtil.replace(DevOpsConstant.HelmWrapperAPIUrl.HELM_SEARCH_REPO_GET,":keyword",chartName.trim());
        JSONObject searchJson = HuHttpUtil.dealHttpAndResponse(helmSearchRepoUrl);
        int code = searchJson.getInt("code");
        JSONArray data = searchJson.getJSONArray("data");
        if (code == 0 && data != null && data.size() >0){
            result = DevOpsConstant.HelmCheckType.CHECK_SUCCESS;
        }

        return result;
    }

    /**
     * @Author heyabo
     * @Description 异步处理chart状态
     * @Date 16:56 2022/3/31
     * @Param [chart, queryWrapper]
     * @return void
     **/
    private void execAsync(DOSHelmChart chart, LambdaQueryWrapper<DOSHelmChart> queryWrapper) {
        ThreadPoolSingleton.getInstance().executeTask(() -> {
            log.info("开始异步处理...");
            try {
                Integer chartStatus = checkChart(chart);
                setStatus(chart, queryWrapper, chartStatus);
            }catch (Exception e){
                setStatus(chart, queryWrapper, DevOpsConstant.HelmCheckType.CHECK_FAILURE);
            }
            log.info("异步处理结束");
        });
    }

    /**
     * @Author heyabo
     * @Description 设置status
     * @Date 16:50 2022/4/1
     * @Param [chart, queryWrapper, chartStatus]
     * @return void
     **/
    private void setStatus(DOSHelmChart chart, LambdaQueryWrapper<DOSHelmChart> queryWrapper, Integer chartStatus) {
        if (null == chart.getId()) {
            DOSHelmChart helmChart = baseMapper.selectOne(queryWrapper);
            helmChart.setChartStatus(chartStatus);
            baseMapper.updateById(helmChart);
        } else {
            chart.setChartStatus(chartStatus);
            baseMapper.updateById(chart);
        }
    }

    /**
     * @Author heyabo
     * @Description 截取数字
     * @Date 15:14 2022/4/20
     * @Param [content]
     * @return java.lang.String
     **/
    public Matcher getNumbers(String content) {
        String reg = "^Pushing (.*)-(\\d+.\\d+.\\d+).tgz to";
        Pattern pattern = Pattern.compile(reg);

        return pattern.matcher(content);
    }
}
